#include "simple_render_system.hpp"
#include <iostream>
#include <stdexcept>
#include <array>

#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>


namespace njm {

    struct SimplePushConstantData
    {
        glm::mat4 modelMat{1.0f};
        glm::mat4 normalMat{1.0f};
    };
    
    SimpleRenderSystem::SimpleRenderSystem(
        NjmDevice &device, VkRenderPass renderPass, VkDescriptorSetLayout globalSetLayout,
        std::string vertShaderPath, std::string fragShaderPath) 
        : njmDevice{device}, vertShaderPath{vertShaderPath}, fragShaderPath{fragShaderPath}
    {
        createPipelineLayout(globalSetLayout);
        createPipeline(renderPass);
    }
    SimpleRenderSystem::~SimpleRenderSystem()
    {
        vkDestroyPipelineLayout(njmDevice.device(), pipelineLayout, nullptr);
    }

    void SimpleRenderSystem::createPipelineLayout(VkDescriptorSetLayout globalSetLayout)
    {
        VkPushConstantRange pcRange{};
        pcRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
        pcRange.offset = 0;
        pcRange.size = sizeof(SimplePushConstantData);

        std::vector<VkDescriptorSetLayout> setLayouts{globalSetLayout};

        VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
        pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
        pipelineLayoutInfo.setLayoutCount = static_cast<uint32_t>(setLayouts.size());
        pipelineLayoutInfo.pSetLayouts = setLayouts.data();
        pipelineLayoutInfo.pushConstantRangeCount = 1;
        pipelineLayoutInfo.pPushConstantRanges = &pcRange;

        if (vkCreatePipelineLayout(njmDevice.device(), &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS)
        {throw std::runtime_error("SimpleRenderSystem::createPipelineLayout : failed to create pipelineLayout");}      
    }

    void SimpleRenderSystem::createPipeline(VkRenderPass renderPass)
    {
        assert(pipelineLayout != nullptr && "SimpleRenderSystem::createPipeline : pipelineLayout is nullptr");
        PipelineConfigInfo pipelineConfig{};
        NjmPipeline::defaultPipelineConfigInfo(pipelineConfig);
        pipelineConfig.renderPass = renderPass;
        pipelineConfig.pipelineLayout = pipelineLayout;

        njmPipeline = std::make_unique<NjmPipeline>(
            njmDevice, 
            vertShaderPath, fragShaderPath,
            pipelineConfig);
    }

    void SimpleRenderSystem::renderGameObjs(FrameInfo &frameInfo)
    {
        njmPipeline->bind(frameInfo.commandBuffer);
        vkCmdBindDescriptorSets(frameInfo.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
            pipelineLayout, 0, 1, &frameInfo.globalDescriptorSet, 0, nullptr);

        for (auto &kv : frameInfo.gameObjects)
        {
            auto &obj = kv.second;
            if (obj.model == nullptr) {continue;}
            
            SimplePushConstantData pc{};
            pc.modelMat = obj.transform.mat4();
            pc.normalMat = obj.transform.normalMatrix();
            vkCmdPushConstants(frameInfo.commandBuffer, pipelineLayout, 
                VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,              
                0, sizeof(SimplePushConstantData), &pc);

            obj.model->bind(frameInfo.commandBuffer);
            obj.model->draw(frameInfo.commandBuffer);
        }       
    }

}